// 
//  av-server.vala
//  
//  Author:
//       Alexandre Rosenfeld <alexandre.rosenfeld@gmail.com>
//  
//  Copyright (c) 2011 Alexandre Rosenfeld
// 
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Lesser General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
// 
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Lesser General Public License for more details.
// 
//  You should have received a copy of the GNU Lesser General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.

namespace mShare.AirVideo {
	
	struct Method {
		MethodHandler handler;
	}
	
	struct Service {
		HashTable<string,Method?> methods;
	}
	
	delegate Value? MethodHandler(Soup.Server server, Soup.Message msg, Value[] parameters);
	
	class Server : Object {
		private bool _active = false;
		private HashTable<string,Service?> services;
		
		public bool active { 
			get { return _active; }
			set { (value) ? start() : stop(); }
		}
		public Soup.Server server { get; private set; } 
		public uint port { get; private set; }
		
		private Zeroconf.Publisher publisher;
		
		static const int PORT = 45632;
		
		public Server () {
			this.services = new HashTable<string,Service?>(str_hash, str_equal);
			
			this.add_method("browseService", "getPathItems", this.getPathItems);
			this.add_method("browseService", "getItems", this.getItems);
		}
		
		protected Value? getPathItems(Soup.Server server, Soup.Message msg, Value[] parameters) {
			var items = new ValueArray(0);
			
			var root = new PathItem();
			root.name = Environment.get_host_name();
			
			items.append(root);
			
			return items;
		}
		
		protected Value? getItems(Soup.Server server, Soup.Message msg, Value[] parameters) {
			if (parameters.length > 0) {
				var browseRequest = parameters[0];
			}
			return null;
		}
		
		private void service_handler(Soup.Server server, Soup.Message msg, string path, HashTable ?query, Soup.ClientContext client) {
			debug ("Path: %s", path);
			
			try {
				if (msg.request_body.length == 0) {
					warning("Request data is empty");
					return;
				}
				Value? data = parse(new MemoryInputStream.from_data(msg.request_body.data, free));
				if (data == null) {
					warning("Request is empty");
					return;
				}
				var request = (SerializableData) data;
				var request_data = request.data;
				string serviceName = (string) request_data.lookup("serviceName");
				string methodName = (string) request_data.lookup("methodName");
				unowned ValueArray? parameters = request_data.lookup("parameters") as ValueArray;
				
				debug("Service: %s, method: %s", serviceName, methodName);
				
				var response = new Response();
				
				Service? service = this.services.lookup(serviceName);
				Method? method = service.methods.lookup(methodName);
				method.handler(server, msg, parameters.values);
				
				//if (serviceName == "browseService") {
				//	response.result = this.browse_service_handler(server, msg, methodName, parameters.values);
				//}
				
				debug("Serializing...");
				
				var response_stream = new MemoryOutputStream(null, realloc, free);
				serialize(response_stream, response);
				
				unowned uint8[] response_data = response_stream.get_data();
				response_data.length = (int)response_stream.get_data_size();
				
				msg.set_response("application/airvideo", Soup.MemoryUse.COPY, response_data);

				msg.set_status (Soup.KnownStatusCode.OK);
			} catch (GLib.IOError e) {
				warning ("Unexpected end of request data");
			} catch (Serialize.Error e) {
				warning ("Could not parse request: %s", e.message);
			}
		}
		
		private void add_method(string service_name, string method_name, MethodHandler handler) {
			Service? service = this.services.lookup(service_name);
			if (service == null) {
				service = Service() {methods = new HashTable<string,Method?>(str_hash, str_equal)};
				this.services.insert(service_name, service);
			}
			Method method = Method() {handler = handler};
			service.methods.insert(method_name, method);
		}
		
		private bool publish() {
			try {
				publisher.publish("AirVideo", this.port, "_airvideoserver._tcp", false, null);
			} catch (Zeroconf.Error e) {
				return false;
			}
			return true;
		}
			
		public bool start() {
			this.server = new Soup.Server(Soup.SERVER_PORT, PORT);
			if (this.server == null) {
				warning ("Unable to start AirVideo server on port %u, trying any open port", port);
				this.server = new Soup.Server(Soup.SERVER_PORT, Soup.ADDRESS_ANY_PORT);
				if (this.server == null)
					return false;
			}
			this.port = this.server.get_port();
			debug ("Started AirVideo server on port %u", this.port);
			
			this.server.add_handler ("/service", this.service_handler);
			
			this.publisher = new Zeroconf.AvahiPublisher();
			this.publish();
			
			this.server.run_async();
			
			this._active = true;
			
			return true;
		}
		
		public bool stop() {
			return true;
		}
	}
}